﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace ALDIOrganizer
{

    /// <summary>
    /// Contains the actual Rota sheet (Shifts and contract_hours), serves as a "sheet" for the RotaPackaging class
    /// </summary>
    /// <remarks>
    /// Sheet doesn't store the information about _staff - for example. It is able to "find out"
    /// who works On
    /// Sheet state is used as a snapshot for Undo/Redo functionality.
    /// </remarks>
    [Serializable]
    public class Template : Sheet, ICloneable, IDeepCopyable<Template>
    {
        #region fields for databinding
        public string Name
        {
            get { return _Name; }
            set { _Name = value; }
        }
        #endregion


        private string _Name;
        
        
        public Template(WorkingHoursSpan[] pWorkingHours, string pName)
        {
            Mode = SheetType.Template;
            _Name = pName;
            WorkingHours = pWorkingHours;
        }

        #region events
        public delegate bool OnWorkingHoursMismatchEventHandler(Dictionary<Shift, Mismatch> mismatchedShifts);
        public delegate void OnWorkingHoursUpdatedEventHandler(WorkingHoursSpan[] pNewHours, Weekday[] pDays);

        public override event OnDataChangedEventHandler OnDataChanged;

        public event OnWorkingHoursMismatchEventHandler OnWorkingHoursMismatch;

        public event OnWorkingHoursUpdatedEventHandler OnWorkingHoursUpdated;
        
        #endregion


        public WorkingHoursSpan[] WorkingHours; // initialize it somewhere

        public void AddRow(Position pos, Guid guid)
        {
            var newRow = SpawnNewRow(SheetType.Template, this, new RowTag(guid));
            SetRoleValue(newRow, pos);
            Cells.Add(newRow);
            Modified = true;
        }

        public void RemoveRow(Guid guid)
        {
            if (this.Mode != SheetType.Template)
            {
                return;
            }
            var rowInQuestion = this[guid];
            Cells.Remove(rowInQuestion);
        }

        internal void SetRoleValue(Cell[] row, Position new_role)
        {
            if (new_role != row[0].RoleValue)
                Modified = true;
            foreach (var c in row)
                c.RoleValue = new_role;            
            
        }

        internal void SetWorkingHours(WorkingHoursSpan hrs, Weekday[] days)
        {
            #region take care of mismatches
            var shiftsAffected = new List<Dictionary<Shift, Mismatch>>();
            foreach (var day in days)
            {
                var shiftsForThisDay = GetAllShiftsForColumn(day.ToInt1());
                var shiftsAffectedOnThisDay = Shift.GetMismatchedShifts(
                    shiftsForThisDay.ToArray(),
                    hrs);
                if (shiftsAffectedOnThisDay.Count > 0)
                {
                    shiftsAffected.Add(shiftsAffectedOnThisDay);
                }
            }

            if (shiftsAffected.Count > 0)
            {                
                /* what we have is a list of dictionaries - SelectMany combines them, flattens them into one big dictionary
                 * that's the "combinedDictionary" */
                var combinedDictionary = shiftsAffected.SelectMany(dic => dic).ToDictionary(
                    kv => kv.Key,
                    kv => kv.Value);
                /* triggering the event, in the eventhandler (we hope :)) user will
                 * be asked whether he wants to proceed or not */
                
                var proceed = true; // by default (if there's no event handler to call and ask) - we're gonna go ahead
                if (OnWorkingHoursMismatch != null)
                {
                    proceed = OnWorkingHoursMismatch(combinedDictionary);
                }
                if (!proceed)   // user chose to cancel 
                {
                    return;     // - good night
                }
            }
            #endregion

            #region actually update working hours
            foreach (var day in days)
            {
                WorkingHours[day.ToInt0()] = hrs;
                RefreshColumn(day);
            }
            OnWorkingHoursUpdated(WorkingHours, days);
            #endregion
            Sounds.Play(Sounds.Validated);
            Modified = true;
        }

        internal virtual void AddShifts(ShiftTandem shifts, Weekday day, RowTag rowtag, bool refreshAndClick)
        {
            foreach (Shift s in shifts)
            {
                s.Day = day;
                s.JobPosition = this[rowtag.GuidValue][0].RoleValue;
                addShift(day, rowtag, s);
            }
            Modified = true;
            var cell = this[rowtag][day.ToInt1()];
            if (!refreshAndClick)
            {
                return;
            }
            // OnMustRecalculateDiscrepancies(new RowTag[] { rowtag },
            // new Weekday[] { Day });
            cell.Update();                
            OnDataChanged(
                new[] { rowtag },
                new[] { day });
            Sounds.Play(Sounds.Validated);
        }

        internal virtual void RemoveShifts(Weekday day, RowTag rowtag, bool refreshAndClick)
        {
            var cell = this[rowtag][day.ToInt1()];
            removeShifts(cell);
            Modified = true;
            if (!refreshAndClick)
            {
                return;
            }
            // OnMustRecalculateDiscrepancies(new RowTag[] { rowtag }, new Weekday[] { Day });
            OnDataChanged(
                new[] { rowtag },
                new[] { day });
            Sounds.Play(Sounds.Validated);
        }

        internal virtual void RemoveShiftsEntireRow(RowTag pRowTag, bool pRefreshAndClick)
        {
            for (var day = 1; day < 8; day++)
            {
                var cell = this[pRowTag][day];
                removeShifts(cell);
            }
            Modified = true;
            if (!pRefreshAndClick)
            {
                return;
            }
            // OnMustRecalculateDiscrepancies(new RowTag[] { pRowTag }, Time.AllWeekdays);
            OnDataChanged(
                new[] { pRowTag },
                Time.AllWeekdays);
            Sounds.Play(Sounds.Validated);
        }

        internal int ShiftsCount(Weekday[] pDays)
        {
            return pDays.Sum(day =>
                GetAllShiftsForColumn((int) day + 1).Count());
        }

        #region ICloneable Members

        public object Clone()
        {
            return Cloner.Copy(this);
        }
                

        public Template DeepCopy()
        {
            var copy = new Template(
                Cloner.Copy(WorkingHours),
                Name);

            SetParent(null);

            copy.Cells = Cloner.Copy(Cells);
            copy._Name = Name;
            copy.WorkingHours = Cloner.Copy(WorkingHours);
            
            copy.SetParent(copy);
            copy.ActivateCellStyles();
            copy.ResetTracker();
            SetParent(this);
            return copy;
        }

        #endregion

        
        internal void SetParent(Template pParent)
        {
            foreach (var shift in GetAllShifts)
            {
                shift.SetParent(pParent);
            }
            foreach (var cell in GetAllCells)
            {
                cell.SetParent(pParent);
            }
        }



        internal IEnumerable<Cell> GetAllCells
        {
            get
            {
                return GetAllRows.SelectMany(row => row);
            }
        }

        internal static string DefaultName
        {
            get
            {
                return Settings.Default.TEMPLATE_DefaultName;
            }
        }

        internal static bool ValidateName(string pProposedName)
        {
            return Regex.IsMatch(
                pProposedName,
                Settings.Default.REGEX_LegalTemplateName);
        }

        internal static string SuggestAlternativeName(string pOriginalName, byte pNumberToAdd)
        {
            return String.Concat(
                pOriginalName,
                " ",
                pNumberToAdd.ToString("00"));
        }

        internal static WorkingHoursSpan[] DefaultHours
        {
            get
            {
                var hr = default(TimeByQuarters);
                var hr2 = default(TimeByQuarters);

                var result = new WorkingHoursSpan[7];

                for (var i = 0; i < 7; i++)
                {
                    if (i < 5) // Monday - Friday
                    {
                        hr.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_MonFri_Open;
                        hr2.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_MonFri_Close;
                    }
                    if (i == 5) // Saturday
                    {
                        hr.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_Sat_Open;
                        hr2.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_Sat_Close;
                    }
                    if (i == 6) // Sunday
                    {
                        hr.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_Sun_Open;
                        hr2.Time = Settings.Default.TEMPLATE_DEFAULTWORKINGHOURS_Sun_Close;
                    }

                    result[i].Open = hr;
                    result[i].Close = hr2;
                }
                return result;
            }
        }

        public void AllRefresh()
        {
            if (OnDataChanged != null)
            {
                OnDataChanged(
                    GetAllTags,
                    Time.AllWeekdays);
            }
        }
    }
}
